#pragma once
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

#include "Socket.hpp"
using namespace std;
static const int fd_num_max = sizeof(fd_set) * 8;
const int default_fd = -1;
//底层结构: 位图
class SelectServer
{
private:
    Socket _listensock;
    uint16_t _port = 8080;
    int fd_arry[fd_num_max];

public:
    SelectServer(uint16_t port)
        :_port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
            fd_arry[i] = default_fd;
        
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

public:
    bool Init();
    void Start();                       //监听文件描述符(连接到来 or 资源就绪)
    void SendEvent(fd_set& rfds);       //根据情况派发事件
    void NewLink();                     //连接到来
    void ParseData(int fd, int pos);    //资源就绪

};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool SelectServer::Init()
{
    _listensock.Create();
    _listensock.Bind(_port);
    _listensock.Listen();

    return true;
}

void SelectServer::Start()
{
    int listensock = _listensock.GetSockfd();
    fd_arry[0] = listensock;
    fd_set rfds;
    FD_ZERO(&rfds);

    while(1)
    {
        //rfds是一个输入输出型参数: 开始设置要监听的文件描述符, 回来的时候查看是否有文件描述符就绪-->每次都需要重新设置
        int maxfd = fd_arry[0];
        for(int i = 0; i < fd_num_max; i++)
        {
            
            if(fd_arry[i] == default_fd) continue;
            FD_SET(fd_arry[i], &rfds);      //设置要监听文件描述符 --_listensock来获取新连接, 其它的fd来监听是否可读

            maxfd = max(maxfd, fd_arry[i]);
        }
        struct timeval timeout = {1 , 0};   //设置超时时间

        //select监听
        int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
        switch (n)
        {
        case 0:
            cout << "time out: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
            break;
        case -1:
            cerr << "select error" << endl;
            break;
        default:
            //事件就绪: 可能是新连接 or 可以读取数据了
            cout << "get a new link" << endl;   
            SendEvent(rfds); 
            break;
        }
    }
}

// io = 等 + 拷贝
//所有合法的文件描述符在fd_arry中, select返回后就绪的文件描述符在rfds中
void SelectServer::SendEvent(fd_set& rfds)
{
    for(int i = 0; i < fd_num_max; i++)
    {
        // 检查就绪的文件描述符
        int fd = fd_arry[i];
        if(fd == default_fd)continue;

        if(FD_ISSET(fd, &rfds))//可读事件就绪 or 新连接来了
        {
            //获取新连接
            if(fd == _listensock.GetSockfd())
            {
                NewLink();
            }
            else //可以读数据了
            {
                ParseData(fd,i);
                cout << "ParseData" << endl;
            }
        }
    }
}

void SelectServer::NewLink()
{
    std:string client_ip;
    uint16_t client_port;
    int newsocket = _listensock.Accept(&client_ip, &client_port);
    if(newsocket < 0)return;
    cout << "accept success--" << "client_ip: " << client_ip << "client_port: "  << client_port << endl;

    //不清楚数据是否就绪, 不能直接读(只有1个进程, 一读就阻塞)
    //那咋办?
    //将新获取的连接添加到fd数组里, 下次循环select会重新将文件描述符添加到rfds里--交给select监听--数据就绪就可以读取了
    //找到空闲位置方进去
    int pos = 1;
    for( ; pos = 1 < fd_num_max; pos++)
    {
        if(fd_arry[pos] != default_fd)continue;
        else break;
    }

    //满了--放不下了, 否则直接放
    if(pos == fd_num_max)
    {
        cout << "server is full" << endl;
        close(newsocket);
    }
    else
    {
        fd_arry[pos] = newsocket;
    }
}

void SelectServer::ParseData(int fd, int i)
{
    char buffer[1024];
    ssize_t n =  read(fd, buffer, sizeof(buffer) - 1); //我们不一定能保证读取的数据保证--协议定制--需要处理 
    if(n > 0)
    {
        buffer[n] = 0;
        cout << "get a message: " << buffer <<endl;
    }
    else if(n == 0)
    {
        cout << "client quit, fd is " << fd << endl;
        close(fd);
        fd_arry[i] = default_fd;    
    }
}

